﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics.Contracts;

namespace Common
{
    class HTMLDownloader
    {
        private static string _userAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
        private static string _referUrl;
        private static CookieContainer _defaultCookieContainer = new CookieContainer();
        private CookieContainer _cookieContainer;

        /// <summary>
        /// 指定的本地IP
        /// </summary>
        private IPAddress _localIp;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="enableCookie">启用Cookie</param>
        /// <param name="cookieContainer">Cookie容器</param>
        /// <param name="localIP">本地IP，有时本地有多个网卡，或者一个网卡配置了多个IP地址，可以通过指定的IP来发送请求</param>
        public HTMLDownloader(bool enableCookie = true, CookieContainer cookieContainer = null, IPAddress localIP = null)
        {
            if (enableCookie)
            {
                if (cookieContainer != null)
                    _cookieContainer = cookieContainer;
                else
                    _cookieContainer = _defaultCookieContainer;
            }

            this._localIp = localIP;
        }




        /// <summary>
        /// 检测编码
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="defaultEncoding"></param>
        /// <returns></returns>
        private Encoding detectEncoding(byte[] bytes, Encoding defaultEncoding)
        {

            int length = bytes.Length;
            if (length >= 2)
            {

                if (length >= 4)
                {
                    //UTF32，Big-Endian
                    if (isByteArrayStartWith(bytes, 4, 0x00, 0x00, 0xFE, 0xFF))
                        return new UTF32Encoding(true, true);
                    //UTF32，Little-Endian
                    if (isByteArrayStartWith(bytes, 4, 0xFF, 0xFE, 0x00, 0x00))
                        return new UTF32Encoding(false, true);
                }
                //UTF8
                if (length >= 3 && isByteArrayStartWith(bytes, 3, 0xEF, 0xBB, 0xBF))
                    return new UTF8Encoding(true);

                //UTF16，Big-Endian
                if (isByteArrayStartWith(bytes, 2, 0xFE, 0xFF))
                    return new UnicodeEncoding(true, true);
                //UTF16，Little-Endian
                if (isByteArrayStartWith(bytes, 2, 0xFF, 0xFE))
                    return new UnicodeEncoding(false, true);


                string str = defaultEncoding.GetString(bytes);
                var match = Regex.Match(str, "<meta\\s[\\s\\S]*?charset\\s*=\\s*([^\"'>\\s]*)[^>]*>", RegexOptions.IgnoreCase);
                if (match.Success)
                {
                    string encoding = match.Groups[1].Value;
                    if (!string.IsNullOrEmpty(encoding))
                    {
                        try
                        {
                            return Encoding.GetEncoding(encoding);
                        }
                        catch
                        {
                            return defaultEncoding;
                        }
                    }
                }


            }
            return defaultEncoding;
        }

        /// <summary>
        /// 判断字节数组是否以指定字节开始
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="count"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        static bool isByteArrayStartWith(byte[] bytes, int count, params int[] values)
        {
            for (int i = 0; i < count; i++)
                if (bytes[i] != values[i])
                    return false;
            return true;
        }

        /// <summary>
        /// 获取发送请求时使用的本地IP
        /// </summary>
        /// <param name="servicePoint"></param>
        /// <param name="remoteEndPoint"></param>
        /// <param name="retryCount"></param>
        /// <returns></returns>
        public IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
        {
            return new IPEndPoint(this._localIp, 9000);
        }

        /// <summary>
        /// 获取HTML
        /// </summary>
        /// <param name="address">页面地址</param>
        /// <param name="encoding">网页编码，如果为null，自动检测</param>
        /// <returns></returns>
        public string GetHtml(Uri address, Encoding encoding = null)
        {
            Contract.Requires(address != null);

            string rtn = null;
            HttpWebRequest webRequest = null;
            HttpWebResponse webReponse = null;
            Stream stream = null;
            StreamReader sreader;

            try
            {
                webRequest = (HttpWebRequest)WebRequest.Create(address);
                webRequest.UserAgent = _userAgent;
                webRequest.Referer = _referUrl;
                webRequest.CookieContainer = _cookieContainer;
                if (this._localIp != null)
                    webRequest.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(BindIPEndPointCallback);
                webReponse = (HttpWebResponse)webRequest.GetResponse();
                stream = webReponse.GetResponseStream();


                if (encoding != null)
                {
                    sreader = new StreamReader(stream, encoding);
                    rtn = sreader.ReadToEnd();
                }
                else
                {
                    Byte[] bytes;
                    using (BinaryReader br = new BinaryReader(stream))
                    {
                        int length = 500000;
                        if (webReponse.ContentLength > 0)
                            length = (int)webReponse.ContentLength;
                        bytes = br.ReadBytes(length);
                        encoding = detectEncoding(bytes, Encoding.GetEncoding("GB18030"));
                        rtn = encoding.GetString(bytes);
                    }

                }
                _referUrl = address.OriginalString;
            }
            finally
            {
                if (webReponse != null)
                    webReponse.Close();
                if (stream != null)
                    stream.Close();
            }
            return rtn;

        }

        /// <summary>
        /// 获取HTML
        /// </summary>
        /// <param name="address">页面地址</param>
        /// <param name="encoding">网页编码，如果为null，自动检测</param>
        /// <returns></returns>
        public string GetHtml(string address, Encoding encoding = null)
        {
            Uri uri = new Uri(address);
            return GetHtml(uri, encoding);

        }

    }



}
